﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using NClass.CodeGenerator;
using NClass.Core;
using NClass.TestHelper;
using System.IO;

namespace NClass.Test
{
	[TestClass]
	public class CodeGenerationTest : BaseCodeGenerationTest
	{
		#region Overridden Members
		protected override string ActualDirectory
		{
			get { return @"CodeGenerationTest\ActualResults"; }
		}

		protected override string ExpectedDirectory
		{
			get { return @"..\..\CodeGenerator\ExpectedResults"; }
		}
		#endregion     

		#region GenerateProjectFiles Tests
		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void CSharpMaxFieldsFailure()
		{
			string projectName = "CSharpMaxFieldsFailure";
            var language = Resolver.GetResolverLanguage(Resolver.Language.CSharp);

			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;
			var firstClass = Resolver.CreateClass(language, out classLanguage, out model);
			model.Name = "Model";
			firstClass.Name = "MyClass";

			TestHelper.Timer.TimedExecution(1000, () =>
			{
				for (long i = 0; i <= int.MaxValue; i++)
				{
					var field = firstClass.AddField();
					field.Name = "Field" + i.ToString();
					field.Type = "string";
				}
			});

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator g = new Generator(myProject, SolutionType.VisualStudio2008);
			GenerationResult result = g.Generate(outputDirectory);

			//Then we have to open the result...and check it...gonna need some scaffolding here to make this easier
			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest.SimpleCsharpOutputGeneration() -> {0}", errorMessage));
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void CSharpMaxOperationsFailure()
		{
			string projectName = "CSharpMaxOperationsFailure";
            var language = Resolver.GetResolverLanguage(Resolver.Language.CSharp);

			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;
			var firstClass = Resolver.CreateClass(language, out classLanguage, out model);
			model.Name = "Model";
			firstClass.Name = "MyClass";

			TestHelper.Timer.TimedExecution(1000, () =>
			{
				for (long i = 0; i <= int.MaxValue; i++)
				{
					var operation = firstClass.AddMethod();
					operation.Name = "Method" + i.ToString();
					operation.Type = "string";
				}
			});

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator g = new Generator(myProject, SolutionType.VisualStudio2008);
			GenerationResult result = g.Generate(outputDirectory);

			//Then we have to open the result...and check it...gonna need some scaffolding here to make this easier
			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest.SimpleCsharpOutputGeneration() -> {0}", errorMessage));
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void CSharpMaxNestedClassesFailure()
		{
			Assert.Fail("StackOverflow caused by the generator within the test studio");
			string projectName = "CSharpMaxNestedClassesFailure";
            var language = Resolver.GetResolverLanguage(Resolver.Language.CSharp);

			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;
			var firstClass = Resolver.CreateClass(language, out classLanguage, out model);
			model.Name = "Model";
			firstClass.Name = "MyClass";

			ClassType parentClass = firstClass;

			TestHelper.Timer.TimedExecution(1000, () =>
			{
				for (long i = 0; i <= int.MaxValue; i++)
				{
					var childClass = model.AddClass();
					childClass.Name = "Class" + i.ToString();
					model.AddNesting(parentClass, childClass);
					parentClass = childClass;
				}
			});

			var myProject = new Project(projectName);
			myProject.Add(model);

			try
			{
				//make a file generator to output some sort of result
				Generator g = new Generator(myProject, SolutionType.VisualStudio2008);
				GenerationResult result = g.Generate(outputDirectory);

				//Then we have to open the result...and check it...gonna need some scaffolding here to make this easier
				var errorMessage = string.Empty;
				Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
					string.Format("CodeGenerationTest.SimpleCsharpOutputGeneration() -> {0}", errorMessage));
			}
			catch (System.Exception ex)
			{

			}
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void CSharpProjectGenerationFailure()
		{
			try
			{
				//allows us to throw an exception in the target method (which returns false)
				File.Move("Templates/csproj.template", "Templates/csproj.template_");

				string projectName = "CSharpProjectGenerationFailure";
                var language = Resolver.GetResolverLanguage(Resolver.Language.CSharp);

				// Prep output directory
				var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

				// Create a new class for C#
				Model model = null;
				Language classLanguage = null;
				var firstClass = Resolver.CreateClass(language, out classLanguage, out model);
				model.Name = "Model";
				firstClass.Name = "MyClass";

				var myProject = new Project(projectName);
				myProject.Add(model);

				//make a file generator to output some sort of result
				Generator g = new Generator(myProject, SolutionType.VisualStudio2008);
				GenerationResult result = g.Generate(outputDirectory);

				Assert.AreEqual(result, GenerationResult.Error);
			}
			finally
			{
				File.Move("Templates/csproj.template_", "Templates/csproj.template");
			}
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void SimpleCsharpOutputGeneration()
		{
			string projectName = "SimpleCsharpOutputGeneration";
            var language = Resolver.GetResolverLanguage(Resolver.Language.CSharp);

			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;
			var firstClass = Resolver.CreateClass(language, out classLanguage, out model);
			model.Name = "Model";
			firstClass.Name = "MyClass";

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator g = new Generator(myProject, SolutionType.VisualStudio2008);
			GenerationResult result = g.Generate(outputDirectory);

			//Then we have to open the result...and check it...gonna need some scaffolding here to make this easier
			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest.SimpleCsharpOutputGeneration() -> {0}", errorMessage));
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void SimpleCsharp2005OutputGeneration()
		{
			string projectName = "SimpleCsharp2005OutputGeneration";
            var language = Resolver.GetResolverLanguage(Resolver.Language.CSharp);

			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;
			var firstClass = Resolver.CreateClass(language, out classLanguage, out model);
			model.Name = "Model2005";
			firstClass.Name = "MyClass2005";

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator g = new Generator(myProject, SolutionType.VisualStudio2005);
			GenerationResult result = g.Generate(outputDirectory);

			//Then we have to open the result...and check it...gonna need some scaffolding here to make this easier
			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest.SimpleCsharp2005OutputGeneration() -> {0}", errorMessage));
		}
		#endregion

		#region JavaSourceFileGenerator : Write Compositetype
		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void SimpleJavaCompositeTypeGeneration()
		{
            SimpleCompositeTypeGeneration(Resolver.GetResolverLanguage(Resolver.Language.Java), "SimpleJavaCompositeTypeGeneration");
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void JavaCompositeTypeGenerationWithOperations()
		{
            CompositeTypeGenerationWithOperations(Resolver.GetResolverLanguage(Resolver.Language.Java), "JavaCompositeTypeGenerationWithOperations");
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void JavaCompositeTypeGenerationWithNestedTypes()
		{
            CompositeTypeGenerationWithNestedTypes(Resolver.GetResolverLanguage(Resolver.Language.Java), "JavaCompositeTypeGenerationWithNestedTypes");
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void JavaCompositeTypeGenerationWithOtherNestedTypes()
		{
			CompositeTypeGenerationWithOtherNestedTypes(Resolver.GetResolverLanguage(Resolver.Language.Java), "JavaCompositeTypeGenerationWithOtherNestedTypes");
		}
		#endregion

		#region CSharpSourceFileGenerator: WriteCompositeType
		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void SimpleCSharpCompositeTypeGeneration()
		{
            SimpleCompositeTypeGeneration(Resolver.GetResolverLanguage(Resolver.Language.CSharp), "SimpleCSharpCompositeTypeGeneration");
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void CSharpCompositeTypeGenerationWithOperations()
		{
            CompositeTypeGenerationWithOperations(Resolver.GetResolverLanguage(Resolver.Language.CSharp), "CSharpCompositeTypeGenerationWithOperations");
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void CSharpCompositeTypeGenerationWithNestedTypes()
		{
            CompositeTypeGenerationWithNestedTypes(Resolver.GetResolverLanguage(Resolver.Language.CSharp), "CSharpCompositeTypeGenerationWithNestedTypes");
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void CSharpCompositeTypeGenerationWithOtherNestedTypes()
		{
            CompositeTypeGenerationWithOtherNestedTypes(Resolver.GetResolverLanguage(Resolver.Language.CSharp), "CSharpCompositeTypeGenerationWithOtherNestedTypes");
		}

		[TestMethod]
		[TestCategoryAttribute("Code Generation Tests")]
		public void CSharpStructGenerationWithOperations()
		{
            StructGenerationWithOperations(Resolver.GetResolverLanguage(Resolver.Language.CSharp), "CSharpStructGenerationWithOperations");
		}
		#endregion

		#region Shared Private Test Helpers
		private void SimpleCompositeTypeGeneration(Resolver.Language language, string projectName)
		{
			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;

			var firstClass = Resolver.CreateClass(language, out classLanguage, out model);
			model.Name = "Model";
			firstClass.Name = "SimpleClassWithProperties";

			Field field1 = firstClass.AddField();
			field1.Name = "Field1";

			Field field2 = firstClass.AddField();
			field2.Name = "Field2";

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator codeGenerator = new Generator(myProject, SolutionType.VisualStudio2008);
			GenerationResult result = codeGenerator.Generate(outputDirectory);

			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest." + projectName + "() -> {0}", errorMessage));
		}

		private void StructGenerationWithOperations(Resolver.Language language, string projectName)
		{
			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;

			var testStruct = Resolver.CreateStruct(language, out classLanguage, out model);
			model.Name = "Model";
			testStruct.Name = "StructWithOperations";

			Field field1 = testStruct.AddField();
			field1.Name = "Field1";

			field1.AccessModifier = AccessModifier.Private;


			Method stringMethod = testStruct.AddMethod();
			stringMethod.Name = "GetString";
			stringMethod.Type = "string";
			stringMethod.AccessModifier = AccessModifier.Public;

			Method intMethod = testStruct.AddMethod();
			intMethod.AccessModifier = AccessModifier.Private;
			intMethod.Name = "GetInt";
			intMethod.Type = "int";

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator codeGenerator = new Generator(myProject, SolutionType.VisualStudio2008);
			GenerationResult result = codeGenerator.Generate(outputDirectory);

			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest." + projectName + " -> {0}", errorMessage));
		}

		private void CompositeTypeGenerationWithOperations(Resolver.Language language, string projectName)
		{
			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;

			var testClass = Resolver.CreateClass(language, out classLanguage, out model);
			model.Name = "Model";
			testClass.Name = "ClassWithPropertiesAndOperations";

			Field field1 = testClass.AddField();
			field1.Name = "Field1";
			if (language == Resolver.Language.CSharp)
			{
				field1.AccessModifier = AccessModifier.ProtectedInternal;
			}
			else
			{
				field1.AccessModifier = AccessModifier.Protected;
			}

			Field field2 = testClass.AddField();
			field2.Name = "Field2";
			field2.Type = "string";

			Method runMethod = testClass.AddMethod();
			runMethod.Name = "Run";
			runMethod.AccessModifier = AccessModifier.Public;

			Method privateMethod = testClass.AddMethod();
			privateMethod.AccessModifier = AccessModifier.Private;
			privateMethod.Name = "Init";

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator codeGenerator = new Generator(myProject, SolutionType.VisualStudio2008);
			GenerationResult result = codeGenerator.Generate(outputDirectory);

			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest." + projectName + " -> {0}", errorMessage));
		}

		private void CompositeTypeGenerationWithNestedTypes(Resolver.Language language, string projectName)
		{
			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			//Language classLanguage = Resolver.GetLanguage(Resolver.Language.CSharp);

			var classes = Resolver.CreateClasses(language, out model, "ParentClass", "ChildClass");
			var testClass = classes["ParentClass"];
			var nestedClass = classes["ChildClass"];

			model.Name = "Model";
			testClass.Name = "ParentClass";

			Field field1 = testClass.AddField();
			field1.Name = "Field1";
			if (language == Resolver.Language.CSharp)
			{
				field1.AccessModifier = AccessModifier.ProtectedInternal;
			}
			else
			{
				field1.AccessModifier = AccessModifier.Protected;
			}

			Field field2 = testClass.AddField();
			field2.Name = "Field2";
			field2.Type = "string";

			Method runMethod = testClass.AddMethod();
			runMethod.Name = "Run";
			runMethod.AccessModifier = AccessModifier.Public;

			Method privateMethod = testClass.AddMethod();
			privateMethod.AccessModifier = AccessModifier.Private;
			privateMethod.Name = "Init";

			//Setting up nested classes
			nestedClass.Name = "ChildClass";

			nestedClass.AddConstructor();

			if (language == Resolver.Language.CSharp)
			{
				Property childProperty = nestedClass.AddProperty();
				childProperty.Name = "Name";
				childProperty.AccessModifier = AccessModifier.Public;
				childProperty.Type = "string";
			}

			model.AddNesting(testClass, nestedClass);

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator codeGenerator = new Generator(myProject, SolutionType.VisualStudio2008);
			GenerationResult result = codeGenerator.Generate(outputDirectory);

			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest" + projectName + "() -> {0}", errorMessage));
		}

		private void CompositeTypeGenerationWithOtherNestedTypes(Resolver.Language language, string projectName)
		{
			// Prep output directory
			var outputDirectory = PrepareGenerationOutputDirectory(language, projectName);

			// Create a new class for C#
			Model model = null;
			Language classLanguage = null;

			var testClass = Resolver.CreateClass(language, out classLanguage, out model);
			model.Name = "Model";
			testClass.Name = "ParentClass";

			Field field1 = testClass.AddField();
			field1.Name = "Field1";
			if (language == Resolver.Language.CSharp)
			{
				field1.AccessModifier = AccessModifier.ProtectedInternal;
			}
			else
			{
				field1.AccessModifier = AccessModifier.Protected;
			}

			Field field2 = testClass.AddField();
			field2.Name = "Field2";
			field2.Type = "string";

			Method runMethod = testClass.AddMethod();
			runMethod.Name = "Run";
			runMethod.AccessModifier = AccessModifier.Public;

			Method privateMethod = testClass.AddMethod();
			privateMethod.AccessModifier = AccessModifier.Private;
			privateMethod.Name = "Init";

			EnumType statusEnum = model.AddEnum();
			statusEnum.Name = "Status";
			statusEnum.AddValue("Pending");
			statusEnum.AddValue("Submitted");
			statusEnum.AddValue("Recieved");
			model.AddNesting(testClass, statusEnum);

			if (language == Resolver.Language.CSharp)
			{
				DelegateType delegateNestedtype = model.AddDelegate();
				delegateNestedtype.Name = "OnChanging";
				delegateNestedtype.AddParameter("object target");
				delegateNestedtype.AddParameter("double price");
				model.AddNesting(testClass, delegateNestedtype);
			}

			var myProject = new Project(projectName);
			myProject.Add(model);

			//make a file generator to output some sort of result
			Generator codeGenerator = new Generator(myProject, SolutionType.VisualStudio2008);
			GenerationResult result = codeGenerator.Generate(outputDirectory);

			//Then we have to open the result...and check it...gonna need some scaffolding here to make this easier
			var errorMessage = string.Empty;
			Assert.IsTrue(ValidateOutput(language, projectName, out errorMessage),
				string.Format("CodeGenerationTest." + projectName + "() -> {0}", errorMessage));
		}
		#endregion

		#region Generated Output Comparison
		//MOVED TO BASE CLASS TO SHARE
		//private static readonly string _actualDirectory = @"CodeGenerationTest\ActualResults";
		//private static readonly string _expectedDirectory = @"..\..\CodeGenerator\ExpectedResults";
		//private static string PrepareGenerationOutputDirectory(Resolver.Language language, string projectName)
		//{
		//    var targetDirectory = Path.Combine(_actualDirectory, language.ToString(), projectName);

		//    if (Directory.Exists(targetDirectory))
		//    {
		//        var files = Directory.GetFiles(targetDirectory, "*.*", SearchOption.AllDirectories);

		//        foreach (var fileName in files)
		//            File.Delete(fileName);

		//        Directory.Delete(targetDirectory, true);
		//    }

		//    return Path.Combine(_actualDirectory, language.ToString());
		//}

		//private static bool ValidateOutput(Resolver.Language language, string projectName, out string errorMessage)
		//{
		//    errorMessage = string.Empty;
		//    var expectedDir = Path.Combine(Directory.GetCurrentDirectory(), _expectedDirectory, language.ToString(), projectName);
		//    var actualDir = Path.Combine(_actualDirectory, language.ToString(), projectName);

		//    var missingFiles = new List<string>();
		//    var filesWithDifferences = new List<string>();

		//    if (!Directory.Exists(expectedDir))
		//        errorMessage = string.Format(expectedDir + "There are no expected results for the {0} version of the {1} project", language.ToString(), projectName);
		//    else if (!Directory.Exists(actualDir))
		//        errorMessage = string.Format("There are no actual results for the {0} version of the {1} project", language.ToString(), projectName);
		//    else
		//    {
		//        var expectedFiles = Directory.GetFiles(expectedDir, "*.*", SearchOption.AllDirectories);
		//        var actualFiles = Directory.GetFiles(actualDir, "*.*", SearchOption.AllDirectories).ToList();

		//        foreach (var fileName in expectedFiles)
		//        {
		//            var actualFileName = fileName.Replace(expectedDir, actualDir);

		//            if (actualFiles.Contains(actualFileName))
		//            {
		//                actualFiles.Remove(actualFileName);

		//                //CRC: Added trim for blank space at end of file
		//                if (!string.Equals(File.ReadAllText(fileName).Trim(),
		//                    File.ReadAllText(actualFileName).Trim(), System.StringComparison.CurrentCulture))
		//                {
		//                    filesWithDifferences.Add(Path.GetFileName(fileName));
		//                }
		//            }
		//            else
		//            {
		//                missingFiles.Add(Path.GetFileName(fileName));
		//            }
		//        }

		//        if (missingFiles.Count > 0)
		//        {
		//            errorMessage = string.Format("Missing Files: {0}", string.Join(",", missingFiles));
		//        }

		//        if (actualFiles.Count > 0)
		//        {
		//            errorMessage = string.Format("{0}Extra Files: {1}",
		//                string.IsNullOrWhiteSpace(errorMessage) ? string.Empty : ", ",
		//                string.Join(",", actualFiles.Select(fileName => { return Path.GetFileName(fileName); })));
		//        }

		//        if (filesWithDifferences.Count > 0)
		//        {
		//            errorMessage = string.Format("{0}Files with Differences: {1}",
		//                string.IsNullOrWhiteSpace(errorMessage) ? string.Empty : ", ",
		//                string.Join(",", filesWithDifferences));
		//        }
		//    }

		//    return string.IsNullOrWhiteSpace(errorMessage);
		//}

		#endregion Generated Output Comparison
	}
}
